package com.espirit.eap.manager;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;

import com.espirit.eap.ValidationUtil;
import com.espirit.eap.pagelayout.ModelService;
import com.espirit.eap.sa.Privilege;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.hibernate.HibernateUtil;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.Executor;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.Functions;

@Service
public class I18nService {
	
	private final static Logger logger = Logger.getLogger(I18nService.class);
	
	@PersistenceContext(unitName="espirit")
	private EntityManager em;
	
	@Resource
	private GenericService genericService;
	
	@Resource
	private ValidationUtil serviceUtil;
	
	@Resource
	private ModelService modelService;
	
	@Resource
	private BeanIntrospector beanIntrospector;
	
	@Resource
	private Functions functions;
	
	@Resource
	private CacheService cacheService;
	
	@Resource
	private ObjectFactory objectFactory;
	
	public final static String EMPTY_I18N_KEY = "";

	private Map<String, I18n> cache;
	
	// an interface for other component to amend the i18n temporarily, see PageService.updateElement
	// TODO: when to clear the value
	private Map<String, I18n> tmpCache;
	
	@PostConstruct
	private void init() {
		functions.registCommonService("i18n", this);
		
		cache = cacheService.newCache(I18n.class);
		tmpCache = cacheService.newCache(I18n.class);
	}
	
	public I18n getI18n(String key){
		return getI18n(key, true);
	}
	
	public I18n getI18n(String key, boolean autoCreation){
		return getI18n(key, autoCreation, false);
	}
	
	public I18n getI18nNoCache(String key) {
		String ql = " from " + I18n.class.getName() + " a where a.key=?";
		I18n i18n = (I18n) genericService.searchByQl(new QLInfo(ql, true, key), false).getData(0);
		return i18n;
	}
	
	public I18n getI18nAny(String key) {
		String anyKey = key + '*';
		I18n i18n = cache.get(anyKey);		// any key
		if (i18n == null) {					// exact key
			i18n = getI18n(key, false);
		}
		if (i18n == null) {					// check any one key
			String ql = " from " + I18n.class.getName() + " a where a.key like ?";
			i18n = (I18n) genericService.searchByQl(new QLInfo(ql, true, key + '%'), false).getData(0);
			if (i18n != null) {
				cache.put(anyKey, i18n);	// store it to any key
			}
		}
		return i18n;
	}
	
	public I18n getI18n(String key, boolean autoCreation, boolean writeDb){
		if (key == null) {
			return null;
		}
		I18n i18n = tmpCache.get(key);
		if (i18n != null) {
			return i18n;
		}
		
		i18n = cache.get(key);
		
		// load from db and update the cache
		if (i18n == null) {
			String ql = " from " + I18n.class.getName() + " a where a.key=?";
			try {
				i18n = (I18n) genericService.searchByQl(new QLInfo(ql, true, key), false).getData(0);
			} catch (Exception ex) {
				logger.debug("Failed to load I18n, try to load it again in the new transaction", ex);
				i18n = genericService.executeInNewTx(new Executor<I18n, QLInfo>() {
					@Override
					public I18n execute(QLInfo qlInfo) {
						return (I18n) genericService.searchByQl(qlInfo, false).getData(0);
					}
				}, new QLInfo(ql, true, key));
			}
			if (i18n == null) {
				i18n = new I18n();
				i18n.setKey(key);
				int index = key.lastIndexOf('.');
				if (index > 0) {
					i18n.setCnName(key.substring(index + 1));
				} else {
					i18n.setCnName(key);
				}
			} else {
				i18n = (I18n)HibernateUtil.unproxy(i18n);
			}
			
			cache.put(key, i18n);
		}
		
		// update db & cache
		if (writeDb && i18n.getId() == null) {
			I18n dbI18n = genericService.add(i18n);
			i18n.setId(dbI18n.getId());
		}
		
		// treat the invalid i18n as null if autoCreation is false
		if (i18n.getId() == null) {
			return autoCreation ? i18n : null;
		} else {
			return i18n;
		}
	}
	
	
	public I18n load(Integer id) {
		I18n i18n = genericService.load(I18n.class, id);
		return i18n;
	}

	public I18n add(I18n model){
		I18n existed = getI18n(model.getKey(), false);
		if (existed != null) {
			return existed;
		} else {
			I18n dbI18n = genericService.merge(model);
			cache.put(dbI18n.getKey(), dbI18n);
			return dbI18n;
		}
	}
	
	public I18n update(I18n model) {
		model = genericService.update(model);
		cache.remove(model.getKey());
		return model;
	}
	
	public void removeCache(String key) {
		cache.remove(key);
	}
	
	public boolean deleteList(List<I18n> results) {
		StringBuilder sb = new StringBuilder();
		for (I18n i18n : results) {
			if (i18n != null && i18n.getId() != null) {
				try {
					genericService.delete(i18n);
				} catch (Exception ex) {
					sb.append(i18n.getKey()).append(',');
				}
			}
		}
		
		if (sb.length() > 0) {
			throw new ApplicationException("errors.reference", sb.substring(0, sb.length() - 1));
		}
		return true;
	}

	public I18n updateByKey(I18n i18n) {
		if (i18n.getKey() == null) {
			throw new ApplicationException("errors.missedKey");
		}
		
		I18n existed = getI18n(i18n.getKey(), false, false);
		if (existed != null && existed.getId() == null) {		// found dirty data
			existed = getI18nNoCache(i18n.getKey());			// reload it
		}

		if (existed != null) {
			// don't care about the id
			// update the name
			if (i18n.getCnName() != null) {
				existed.setCnName(i18n.getCnName());
			}
			if (i18n.getEnName() != null) {
				existed.setEnName(i18n.getEnName());
			}
			i18n = genericService.merge(existed);
			cache.remove(i18n.getKey());
		} else {
			// it's new key
			i18n = genericService.merge(i18n);
			cache.put(i18n.getKey(), i18n);
		}
		
//		i18n = data.get(0);
//		logger.error("Found dupicated i18n key: " + i18n.getKey() + ", skip update the i18n name");
		
		return i18n;
	}
	
	@Protected
	public I18n addTempI18n(I18n i18n){
		return tmpCache.put(i18n.getKey(), i18n);
	}
	@Protected
	public I18n removeTempI18n(String i18nKey){
		if (i18nKey == null) {
			return null;
		}
		return tmpCache.remove(i18nKey);
	}

	
	public I18n addI18n(Privilege privilege,String cnName,String enName){
		if (privilege == null) return null;
		return addI18n("privilege." + privilege.getName(),cnName,enName);
	}
	
	public I18n checkAndaddI18n(String key,String cnName,String enName){
		I18n existed = getI18n(key, false);
		if (existed != null) {
			existed.setCnName(cnName);
			existed.setEnName(enName);
			existed = genericService.merge(existed);
			return existed;
		} else {
			return addI18n(key, cnName, enName);
		}
	}
	
	
	public I18n addI18n(String key,String cnName,String enName){
		I18n i18n = new I18n();
		i18n.setKey(key);
		i18n.setCnName(cnName);
		i18n.setEnName(enName);
		return add(i18n);
	}
	
	public I18n addI18n(String cnName,String enName){
		I18n i18n = new I18n();
		i18n.setKey(cnName);
		i18n.setCnName(cnName);
		i18n.setEnName(enName);
		return add(i18n);
	}
	
	// it's a job to clean the invalid i18n
	public void cleanInvalidI18n() {
		// FIXME : check & remove the invalid i18n
		// 1. set time check point
		// 2. use try/catch to check the reference
	}
	
	public List<String> getLanguages() {
		ModelInfo modelInfo = beanIntrospector.getModelInfo(I18n.class.getName());
		List<String> languages = new ArrayList<String>();
		for (PropertyInfo propertyInfo : modelInfo.getAllProperties()) {
			String name = propertyInfo.getName();
			if (name.length() > 4 && name.endsWith("Name")) {
				languages.add(name.substring(0, name.length() - 4));
			}
		}
		return languages;
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public void common() {
		// see '/pages/espirit/i18n.ftl'
	}
    
    public <T> PageInfo<T> search(T model, int start, int limit,
    		String sort, String dir, boolean newSearch, Reference<QLInfo> qlInfoRef) {
    	return genericService.search(model, start, limit, sort, dir, newSearch, qlInfoRef, null, true, false);
    }
    
    public void batchLoad(String ... keys) {
    	if (keys.length == 0) {
    		return ;
    	}
    	
    	StringBuilder ql = new StringBuilder("from ").append(I18n.class.getName()).append(" a ");
    	List<Object> params = new ArrayList<>();
    	boolean isFirst = true;
    	for (String key : keys) {
    		if (cache.containsKey(key)) {
    			continue;
    		}
    		
    		if (isFirst) {
    			isFirst = false;
    			ql.append(" where ");
    		} else {
    			ql.append(" or ");
    		}
    		ql.append("a.key=?");
    		params.add(key);
    	}
    	
    	if (params.size() > 0) {
	    	List<I18n> i18ns = genericService.searchByQl(new QLInfo(ql.toString(), true, params)).getData();
	    	
	    	for (I18n i18n : i18ns) {
	    		cache.put(i18n.getKey(), (I18n)HibernateUtil.unproxy(i18n));
	    	}
	    	
	    	for (String key : keys) {
		    	if (!cache.containsKey(key)) {
					I18n i18n = new I18n();
					i18n.setKey(key);
					int index = key.lastIndexOf('.');
					if (index > 0) {
						i18n.setCnName(key.substring(index + 1));
					} else {
						i18n.setCnName(key);
					}
					cache.put(key, i18n);
				}
	    	}
    	}
    }
    
    @Rest(type = "search")
    public PageInfo<I18n> searchDuplicate() {
    	// Menu, Page, Layout, Element, Model, ModelField
    	String sql = "select b.id"
				+ " from ("
				+ " select i18n, 'menu' as t from saas_menu"
				+ " union all"
				+ " select i18n, 'page' as t from saas_page"
				+ " union all"
				+ " select i18n, 'layout' as t from saas_layout"
				+ " union all"
				+ " select i18n, 'element' as t from saas_element"
				+ " union all"
				+ " select i18n, 'model' as t from saas_model"
				+ " union all"
				+ " select i18n, 'modelfield' as t from saas_modelfield"
				+ " ) a, saas_i18n b where a.i18n <> b.resKey";
    	Query query = em.createNativeQuery(sql);
    	List<Object[]> result = query.getResultList();
    	PageInfo<I18n> pageInfo;
    	if (result.size() > 0) {
    		List<I18n> i18nList = new ArrayList<I18n>();
	    	for (Object item : result) {
	    		Integer id = (Integer) item;
	    		I18n i18n = genericService.load(I18n.class, id);
	    		i18n = i18n.cloneWithId();
	    		i18nList.add(i18n);
	    	}
	    	pageInfo = new PageInfo<>(i18nList);
    	} else {
    		pageInfo = new PageInfo<>();
    	}
    	return pageInfo;
    }
}
